@Max(999)publicint getDetailsCount(){// 使用 JDBC的例子Connection con = null;try{
con = DataSourceConnectionProvider.getByComponent("Invoice").getConnection();// 1String table = MetaModel.get("InvoiceDetail").getMapping().getTable();PreparedStatement ps = con.prepareStatement("select count(*) from " + table +
" where INVOICE_YEAR = ? and INVOICE_NUMBER = ?");
ps.setInt(1, getYear());
ps.setInt(2, getNumber());ResultSet rs = ps.executeQuery();
rs.next();Integer result = newInteger(rs.getInt(1));
ps.close();return result;}catch(Exception ex){
log.error("Problem calculating details count of an Invoice", ex);// You can throw any runtime exception herethrownewSystemException(ex);}finally{try{
con.close();}catch(Exception ex){}}}
JDBC 代码确实丑陋笨拙,但有时确实有助于解决性能问题. DataSourceConnectionProvider 类允许你获取实体数据源的连接 (Invoice in this case).这个类只是为你提供方便, 你也可以使用JNDI或其他方式获取 JDBC连接.事实上,在计算属性中,你可以编写任何Java代码.
如果你使用基于属性的访问, 也就是注解类的getter或 setters, 你必须添加 @Transient 注解到对应的计算属性, 用这种方式:
privatelong number;
@Id @Column(length=10)//注解getter,publiclong getNumber(){// JPA will use property-base access for your classreturn number;}publicvoid setNumber(long number){this.number = number;}
@Transient // You have to annotate as Transient your calculated propertypublicString getZoneOne(){// because you are using property-based accessreturn"In ZONE 1";}
在这种情况下,执行计算器之前,OpenXava会将引用的drivingLicence的type属性的值注入CarrierRemarksCalculator的drivingLicenceType属性,As you see the from attribute supports qualified properties (reference.property). @PropertyValue同样允许属性的值为空:
In this case OpenXava takes the value of the displayed property familyNumber and inject it in the property familyNumber of the calculator; that is @PropertyValue(name="familiyNumber") is equivalent to @PropertyValue(name="familiyNumber", from="familyNumber").
From a calculator you have direct access to JDBC connections, here is an example:
To use JDBC your calculator must implement IJDBCCalculator (1) and then it will receive an IConnectionProvider (2) that you can use within calculate().
OpenXava comes with a set of predefined calculators, you can find them in org.openxava.calculators.
创建时的默认值
You can indicate that the value will be calculated just before creating (inserting into database) an object for the first time.
Usually for the key case you use the JPA standard. For example, if you want to use an identity (auto increment) column as key:
Look at section 9.1.9 of JPA 1.0 specification (part of JSR-220) for learning more about @GeneratedValues.
If you want to use your own logic for generating the value on create, or you want a generated new value for a non-key property then you cannot use the JPA @GeneratedValue, although it's easy to solve these cases using JPA. You only need to add this code to your class:
The JPA @PrePersist annotation does that this method will be executed before inserting the data the first time in database, in this method you can calculate the value for your key or non-key properties with your own logic.
属性校验器
A @PropertyValidator executes validation logic on the value assigned to the property just before storing. A property may have several validators:
The technique to configure the validator (with @PropertyValue) is exactly the same than in calculators. With the attribute onlyOnCreate=”true” you can define that the validation will be executed only when the object is created, and not when it is modified.
The validator code is:
A validator has to implement IPropertyValidator (1), this obliges to the calculator to have a validate() method where the validation of property is executed. The arguments of validate() method are:
Messages errors: A object of type Messages that represents a set of messages (like a smart collection) and where you can add the validation errors that you find.
Object value: The value to validate.
String objectName: Object name of the container of the property to validate. Useful to use in error messages.
String propertyName: Name of the property to validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with errors.add()) by sending a message identifier and the arguments. If you want to obtain a significant message you need to add to your i18n file the next entry:
exclude_string={0} cannot contain {2} in {1}
If the identifier sent is not found in the resource file, this identifier is shown as is; but the recommended way is always to use identifiers of resource files.
The validation is successful if no messages are added and fails if messages are added. OpenXava collects all messages of all validators before saving and if there are messages, then it display them and does not save the object.
The package org.openxava.validators contains some common validators. @PropertyValidator is defined as a Hibernate Validator contraint(new in v3.0.1).
默认校验器(new in v2.0.3)
You can define a default validator for properties depending of its type or stereotype. In order to do it you have to use the file xava/validators.xml of your project to define in it the default validators.
For example, you can define in your xava/validators.xml the following:
This property will be validated using PersonNameValidator although the property itself does not define any validator. PersonNameValidator is applied to all properties with PERSON_NAME stereotype.
You can also assign a default validator to a type.
In validators.xml files you can also define the validators for determine if a required value is present (executed when you use @Required). Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining OpenXava/xava/validators.xml and OpenXavaTest/xava/validators.xml.
Default validators do not apply when you use directly the JPA api for saving your entities.
参照(reference)
A reference allows access from an entity to another entity. A reference is translated to Java code as a property (with its getter and its setter) whose type is the referenced model Java type. For example a Customer can have a reference to his Seller, and that allows you to write code like this:
to access to the name of the seller of that customer.
The syntax of reference is:
@Required // 1
@Id // 2
@SearchKey // 3 New in v3.0.2
@DefaultValueCalculator // 4
@ManyToOne(// 5
optional=false// 1)private type referenceName;// 5public type getReferenceName(){ ... }// 5publicvoid setReferenceName(type newValue){ ... }// 5
@ManyToOne(optional=false) (JPA), @Required (OX) (optional, the JPA is the preferred one): Indicates if the reference is required. When saving OpenXava verifies if the required references are present, if not the saving is aborted and a list of validation errors is returned.
@Id (JPA, optional): Indicates if the reference is part of the key. The combination of key properties and reference properties should map to a group of database columns with unique values, typically the primary key.
@SearchKey (OX, optional): (New in v3.0.2) The search key references are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the @Id members for searching by default, and if the id members are hidden then it uses the first property in the view. With @SearchKey you can choose explicitly references for searching.
@DefaultValueCalculator (OX, one, optional): Implements the logic for calculating the initial value of the reference. This calculator must return the key value, that can be a simple value (only if the key of referenced object is simple) or key object (a special object that wraps the key).
Reference declaration: A regular Java reference declaration with its getters and setters. The reference is marked with @ManyToOne (JPA) and the type must be another entity.
A reference called seller to the entity of Seller entity.
A reference called alternateSeller to the entity Seller. In this case we use fetch=FetchType.LAZY, in this way the data is readed from database on demand. This is the more efficient approach, but it's not the JPA default, therefore it's advisable to use always fetch=FetchType.LAZY when declaring the references.
If you assume that this is in an entity named Customer, you could write:
In a reference @DefaultValueCalculator works like in a property, only that it has to return the value of the reference key.
For example, in the case of a reference with simple key, you can write:
@ManyToOne(optional=false, fetch=FetchType.LAZY) @JoinColumn(name="FAMILY")
@DefaultValueCalculator(value=IntegerCalculator.class, properties=
@PropertyValue(name="value", value="2"))private Family family;
packageorg.openxava.test.calculators;importorg.openxava.calculators.*;/**
* @author Javier Paniza
*/publicclass DefaultWarehouseCalculator implements ICalculator {publicObject calculate()throwsException{
WarehouseKey key = new WarehouseKey();
key.setNumber(4);
key.setZoneNumber(4);return key;}}
Returns an object of type WarehouseKey.
将参照作为键
You can use references as key, or as part of the key. You have to declare the reference as @Id, and use an id class, as following:
@Entity
@IdClass(AdditionalDetailKey.class)publicclass AdditionalDetail {// JoinColumn is also specified in AditionalDetailKey because// a bug in Hibernate, see http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Id @ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Id @Hidden
privateint counter;
...
}
Also, you need to write your key class:
publicclass AdditionalDetailKey implements java.io.Serializable{
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="SERVICE")private Service service;
@Hidden
privateint counter;// equals, hashCode, toString, getters and setters
...
}
You need to write the key class although the key would be only a reference with only a join column.
It's better to use this feature only when you are working against legate databases, if you have control over the schema use an autogenerated id instead.
集合(Collections)
You can define a collection of references to entities. A collection is a Java property of type java.util.Collection.
Here syntax for collection:
@Size (HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
@Condition (OX, optional): Restricts the elements that appear in the collection.
@OrderBy (JPA, optional): The elements in collections will be in the indicated order.
@XOrderBy (OX, optional): The @OrderBy of JPA does not allow to use qualified properties (properties of references). @XOrderBy does allow it.
Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @OneToMany (JPA) or @ManyToMany (JPA) and the type must be another entity.
Let's have a look at some examples. First a simple one:
If you have this within an Invoice, then you are defining a deliveries collection associated to that Invoice. The details to make the relationship are defined in the object/relational mapping.You use mappedBy="invoice" to indicate that the reference invoice of Delivery is used to mapping this collection.
Now you can write a code like this:
Using REMOVE as cascade type produces that when the user removes an invoice its details are also removed.
With @OrderBy you force that the details will be returned ordered by serviceType.
The restriction @Size(min=1) requires at least one detail for the invoice to be valid.
You have full freedom to define how the collection data is obtained, with @Condition you can overwrite the default condition:
@Condition("${warehouse.zoneNumber} = ${this.warehouse.zoneNumber} AND " +
"${warehouse.number} = ${this.warehouse.number} AND " +
"NOT (${number} = ${this.number})")publicCollection getFellowCarriers(){returnnull;}
If you have this collection within Carrier, you can obtain with this collection all carriers of the same warehouse but not himself, that is the list of his fellow workers. As you see you can use this in the condition in order to reference the value of a property of current object. @Condition only applied to the user interface generated by OpenXava, if you call directly to getFellowCarriers() it will be returns null.
If with this you have not enough, you can write the logic that returns the collection. The previous example can be written in the following way too:
publicCollection getFellowCarriers(){Query query = XPersistence.getManager().createQuery("from Carrier c where " +
"c.warehouse.zoneNumber = :zone AND " +
"c.warehouse.number = :warehouseNumber AND " +
"NOT (c.number = :number) ");
query.setParameter("zone", getWarehouse().getZoneNumber());
query.setParameter("warehouseNumber", getWarehouse().getNumber());
query.setParameter("number", getNumber());return query.getResultList();}
As you see this is a conventional getter method. Obviously it must return a java.util.Collection whose elements are of type Carrier.
The references in collections are bidirectional, this means that if in a Seller you have a customers collection, then in Customer you must have a reference to Seller. But it's possible that in Customer you have more than one reference to Seller (for example, seller and alternateSeller) JPA does not know which to choose, becase of this you have the attribute mappedBy of @OneToMany. You can use it in this way:
To indicate that the reference seller and not alternateSeller will be used in this collection.
The @ManyToMany (JPA) anotation allows to define a collection with many-to-many multiplicity. As following:
Methods are the sauce of the objects, without them the object only would be a silly wrapper of data. When possible it is better to put the business logic in methods (model layer) instead of in actions (controller layer).
Finders
A finder is a special static method that allows you to find an object or a collection of objects that follow some criteria.
Some examples:
publicstatic Customer findByNumber(int number)throws NoResultException {Query query = XPersistence.getManager().createQuery("from Customer as o where o.number = :number");
query.setParameter("number", number);return(Customer) query.getSingleResult();}publicstaticCollection findAll(){Query query = XPersistence.getManager().createQuery("from Customer as o");return query.getResultList();}publicstaticCollection findByNameLike(String name){Query query = XPersistence.getManager().createQuery("from Customer as o where o.name like :name order by o.name desc");
query.setParameter("name", name);return query.getResultList();}
As you see, using finder methods creates a more readable code than using the verbose query API of JPA. But this is only a style recomendation, you can choose do not write finder methods and to use directly JPA queries.
Entity validator
An @EntityValidator allows to define a validation at model level. When you need to make a validation on several properties at a time, and that validation does not correspond logically with any of them, then you can use this type of validation.
Its syntax is:
value (required): Class that implements the validation logic. It has to be of type IValidator.
onlyOnCreate (optional): If true the validator is executed only when creating a new object, not when an existing object is modified. The default value is false.
properties (several @PropertyValue, optional): To set a value of the validator properties before executing it.
This validator must implement IValidator (1), this forces you to write a validate(Messages messages) (2). In this method you add the error message ids (3) (whose texts are in the i18n files). And if the validation process (that is the execution of all validators) produces some error, then OpenXava does not save the object and displays the errors to the user.
In this case you see how description and unitPrice properties are used to validate, for that reason the validation is at model level and not at individual property level, because the scope of validation is more than one property.
You can define more than one validator for entity using @EntityValidators, as following:
The @RemoveValidator is a level model validator too, but in this case it is executed just before removing an object, and it has the possibility to deny the deletion.
Its syntax is:
packageorg.openxava.test.validators;importorg.openxava.test.model.*;importorg.openxava.util.*;importorg.openxava.validators.*;/**
* @author Javier Paniza
*/publicclass DeliveryTypeRemoveValidator implements IRemoveValidator {// 1private DeliveryType deliveryType;privateint number;// We use this (instaed of obtaining it from deliveryType)// for testing @PropertyValue for simple propertiespublicvoid setEntity(Object entity)throwsException{// 2this.deliveryType = (DeliveryType) entity;}publicvoid validate(Messages errors)throwsException{if(!deliveryType.getDeliveries().isEmpty()){
errors.add("not_remove_delivery_type_if_in_deliveries", newInteger(getNumber()));// 3}}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}}
As you see this validator must implement IRemoveValidator (1) this forces you to write a setEntity() (2) method that receives the object to remove. If validation error is added to the Messages object sent to validate() (3) the validation fails. If after executing all validations there are validation errors, then OpenXava does not remove the object and displays a list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery type before deleting it.
As in the case of @EntityValidator you can use several @RemoveValidator for entity using @RemoveValidators annotation. @RemoveValidator is executed when you remove entities from OpenXava (using MapFacade or standard OX actions), but not when you use directly JPA. If you want to create a restriction on remove which is recognized by JPA, just use @PreRemove JPA call method.
JPA callback methods
With @PrePersist you can plug in your own logic to execute just before creating the object as persistent object.
As following:
In this case each time that a DeliveryType is created a suffix to description is added.
As you see, this is exactly the same as in other methods but is automatically executed just before creation.
With @PreUpdate you can plug in some logic to execute after the state of the object is changed and just before it is stored in the database, that is, just before executing UPDATE against database.
As following:
In this case whenever that a DeliveryType is modified a suffix is added to its description.
As you see, this is exactly the same as in other methods, but it is executed just before modifying.
You can use all the JPA callback annotations: @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate and @PostLoad.
Embeddable classes
As stated in JPA specification: "An entity may use other fine-grained classes to represent entity state. Instances of these classes, unlike entity instances themselves, do not have persistent identity. Instead, they exist only as embedded objects of the entity to which they belong. Such embedded objects belong strictly to their owning entity, and are not sharable across persistent entities."
The embeddable class syntax is:
@Embeddable (JPA, one, required): Indicates that this class is a embeddable class of JPA, in other words, its instances will be part of persistent objects.
Class declaration: As a regular Java class. You can use extends and implements.
Properties: Regular Java properties.
References: References to entities. This is not supported by JPA 1.0 (EJB 3.0), but the Hibernate implementation support it.
Methods: Java methods with the business logic.
Embedded reference
This example is an embedded (annotated with @Embedded) Address that is referenced from the main entity.
In the main entity you can write:
@Embedded
private Address address;
And you have to define the Address class as embeddable:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;/**
*
* @author Javier Paniza
*/
@Embeddable
publicclass Address implements IWithCity {// 1
@Required @Column(length=30)privateString street;
@Required @Column(length=5)privateint zipCode;
@Required @Column(length=20)privateString city;// ManyToOne inside an Embeddable is not supported by JPA 1.0 (see at 9.1.34),// but Hibernate implementation supports it.
@ManyToOne(fetch=FetchType.LAZY, optional=false) @JoinColumn(name="STATE")privateState state;// 2publicString getCity(){return city;}publicvoid setCity(String city){this.city = city;}publicString getStreet(){return street;}publicvoid setStreet(String street){this.street = street;}publicint getZipCode(){return zipCode;}publicvoid setZipCode(int zipCode){this.zipCode = zipCode;}publicState getState(){return state;}publicvoid setState(State state){this.state = state;}}
As you see an embeddable class can implement an interface (1) and contain references (2), among other things, but it cannot has persistent collections nor to use JPA callbacks methods.
This code can be used this way, for reading:
Customer customer = ...
Address address = customer.getAddress();
address.getStreet();// to obtain the value
Or in this other way to set a new address:
// to set a new address
Address address = new Address();
address.setStreet(“My street”);
address.setZipCode(46001);
address.setCity(“Valencia”);
address.setState(state);
customer.setAddress(address);
In this case you have a simple reference (not collection), and the generated code is a simple JavaBean, whose life cycle is associated to its container object, that is, the Address is removed and created through the Customer. An Address never will have its own life and cannot be shared by other Customer.
Embedded collections
Embedded collections are not supported by JPA 1.0. But you can simulate them using collections to entities with cascade type REMOVE or ALL. OpenXava manages these collections in a special way, as they were embedded collections.
Now an example of an embedded collection. In the main entity (for example Invoice) you can write:
As you see this is a complex entity, with calculators, validators, references and so on. Also you have to define a reference to the container class (invoice). In this case when an Invoice is removed all its details are removed too. Moreover there are differences at user interface level (you can learn more on view chapter).
You can create an OpenXava module for Human and Programmer (not for Identifiable or Nameble directly). In the Programmer module the user can only access to programmers, in the other hand using Human module the user can access to Human and Programmer objects. Moreover when the user tries to view the detail of a Programmer from the Human module the Programmer view will be show. True polymorphism.
About mapping, @AttributeOverrides is supported, but, at the moment, only a single table per class hierarchy mapping strategy works.
复合键
The preferred way for defining the key of an entity is a single autogenerated key (annotated with @Id and @GeneratedValue), but sometimes, for example when you go against legate database, you need to have an entity mapped to a table that uses several column as key. This case can be solved with JPA (therefore with OpenXava) in two ways, using @IdClass or using @EmbeddedId
Id class
In this case you use @IdClass in your entity to indicate a key class, and you mark the key properties as @Id in your entity:
packageorg.openxava.test.model;importjavax.persistence.*;importorg.openxava.annotations.*;importorg.openxava.jpa.*;/**
*
* @author Javier Paniza
*/
@Entity
@IdClass(WarehouseKey.class)publicclass Warehouse {
@Id
// Column is also specified in WarehouseKey because a bug in Hibernate, see// http://opensource.atlassian.com/projects/hibernate/browse/ANN-361
@Column(length=3, name="ZONE")privateint zoneNumber;
@Id @Column(length=3)privateint number;
@Column(length=40) @Required
privateString name;publicString getName(){return name;}publicvoid setName(String name){this.name = name;}publicint getNumber(){return number;}publicvoid setNumber(int number){this.number = number;}publicint getZoneNumber(){return zoneNumber;}publicvoid setZoneNumber(int zoneNumber){this.zoneNumber = zoneNumber;}}
You also need to declare your id class, a serializable regular class with all key properties from the entity:
OpenXava has full support for Hibernate Validator. You can define your own constraints for your entities as explained in Hibernate Validator documention, and OpenXava will recognize them, showing the corresponding validation messages to the user.
Moreover, the OpenXava annotations @Required, @PropertyValidator and @EntityValidator are defined as Hibernate Validator constraints, that means that when you save an entity using directly JPA these validations will apply.
In the other hand, @RemoveValidator, @PropertyValidator(onlyOnCreate=true), EntityValidator(onlyOnCreate=true) and the default validator feature of OpenXava are not recognized by Hibernate Validator or JPA, but only by OpenXava.
Table of Contents
第3章: 模型
在面向对象应用程序的模型层中包含业务逻辑,也就是数据结构和与数据有关的计算、校验和处理过程。OpenXava 是一个面向模型的框架,其中模型最为重要,而其他的(例如,用户界面)依赖于模型。
OpenXava 定义模型的方式是使用纯Java类 (也有一个 XML 版本)。 根据你的模型定义,OpenXava 生成全功能的应用系统。
业务组件(Business Component)
创建OpenXava应用系统的基本单元是业务组件(business component)。 业务组件由名为Entity的Java 类定义 . 这个类是标准的 EJB3 实体, 或者说, 是一个 包含有遵循 Java Persistence API (JPA) 标准注解的POJO 类.JPA 是Java持久性标准, 用于对象将其状态保存到数据库中。如果你知道如何用 POJOs 和 JPA开发, 那你就已经知道如何开发OpenXava应用程序了。
使用一个简单的Java类,你就可以定义一个业务组件,使用:
- 模型: 数据结构, 校验, 计算等.
- 视图: 显示给用户看的模型是什么样.
- 列表数据: 组件中的数据在列表模式下什么样.
- 对象/关系映射: 如何在数据库中存取对象的状态.
在本章中对模型部分进行说明, 包括:结构、校验、计算等。实体(Entity)
要定义模型部分,你必须定义一个带注解的java类。除了自己的注解之外,OpenXava还支持JPA 和Hibernate Validator 注解。这样的java类就是一个实体,也就是一个代表业务概念的持久化类。在本章中JPA代表standard Java Persistent API annotations,HV代表Hibernate Validator annotation,OX代表an annotation ofOpenXava。
实体语法如下:
属性
属性(Properties)代表对象的状态,该状态可以读取,有时也可以更新。对象不负责属性数据的物理存储,只在需要时返回该值。定义属性的语法如下:
版型(Stereotype)
版型 (@Stereotype) 是指定类型特定行为的一种方式. 例如, 名称(name), 备注(comment), 描述(description)等 都对应到Java 类型 java.lang.String ,但你期望每种情况下校验器、缺省大小,可视化编辑器都有所不同;如果你想进行更精确的调整,你可以指定版型。这就是说, 你可以使用这样一些版型 NAME, MEMO or DESCRIPTION对属性进行设置。OpenXava 包含下列通用版型:
- DINERO, MONEY
- FOTO, PHOTO, IMAGEN, IMAGE
- TEXTO_GRANDE, MEMO, TEXT_AREA
- ETIQUETA, LABEL
- ETIQUETA_NEGRITA, BOLD_LABEL
- HORA, TIME
- FECHAHORA, DATETIME
- GALERIA_IMAGENES, IMAGES_GALLERY (setup instructions)
- RELLENADO_CON_CEROS, ZEROS_FILLED
- TEXTO_HTML, HTML_TEXT (text with editable format)
- IMAGE_LABEL, ETIQUETA_IMAGEN (image depending on property content)
- EMAIL
- TELEFONO, TELEPHONE
- WEBURL
- IP
- ISBN
- TARJETA_CREDITO, CREDIT_CARD
- LISTA_EMAIL, EMAIL_LIST
下面展示如何定义你自己的版型. 创建一个 名为PERSON_NAME的版型来标识人员姓名.编辑(或创建) 在目录xava下的文件 editors.xml . 添加:
这样你就定义来编辑器来对版型为 PERSON_NAME的属性来进行编辑或显示.
下一步,定义缺省大小非常有用; 你可以编辑项目中的 default-size.xml文件:
<for-stereotype name="PERSON_NAME" size="40"/>这样,如果你不指定 PERSON_NAME 类型属性的大小,就会使用40作为缺省值.一般情况下不需要改变校验器,但如果你确实需要,你可以在在项目的 validators.xml 文件中添加:
现在一切就绪,你可以将属性定义为PERSON_NAME版型了:
在这个例子中, 属性的大小为40 , 类型为String, 如需要NotBlankCharacterValidator 校验器将执行.
IMAGES_GALLERY 版型
如果你希望组件中的一个属性能够包含图片.你只需要将属性声明为 IMAGES_GALLERY 版型, 像这样:此外, 在映射部分你必须将属性映射到适合存储长度为32为的字符的表列上。
然后,一切完成.
要支持这种版型,你必须对系统进行配置。
首先, 在数据库中创建一个表用于图片存储:
IMAGE 列的类型可以选择你的数据库中存储byte []更合适的类型 (例如 LONGVARBINARY) .
最后,你需要在 persistence/hibernate.cfg.xml 文件中定义映射:
这样你就可以在应用的所有组件中使用IMAGES_GALLERY版型了.
并发和版本属性
并发是应用的一种能力,允许多个用户同时保存数据而不造成数据丢失. OpenXava 使用JPA的开放式并发控制机制. 使用开放式并发控制,记录并不进行锁定,允许高并发而不影响数据的完整性。例如, 如果用户 A 读取了一条记录,然后用户B读取同一条记录,用户B修改记录并保存改变,当用户 A视图保存记录时会收到一条错误信息,他必须重新刷新数据后来进行修改.
要激活实体的并发支持,你只需要定义属性 @Version, 以如下方式:
本属性由持久化引擎使用, 你的应用或用户不能直接使用这一属性.
枚举
OpenXava支持 Java 5 枚举类型. 枚举允许你定义只包含指示值之一的属性.枚举非常易于使用, 例如:
distance 属性只能取这样几个值: LOCAL, NATIONAL 或INTERNATIONAL, 如果不指定 @Required 没有值 (null) 也是允许的.
在用户界面层面,当前的实现使用了下拉框.每个值的标签从i18n 文件中获取.
在数据库层面,值缺省保存为 整型 (0 代表LOCAL, 1 代表NATIONAL, 2 代表INTERNATIONAL ,而null 代表没有取值), 但你也可以配置为其他类型, 在遗留数据库中可以正常使用. 参见 映射 章节.
计算属性
计算属性是只读的 (只有 getter),不能持久化(不对应数据表中的任何列).计算属性用下列方式定义:
根据上述定义,你可以在代码中这样使用:
result 就是 332.772.
属性unitPriceInPesetas展示给用户时是不可编辑的, 编辑器的长度为 10,由@Max(9999999999L) (2)指定. 同时,因为你使用了 @Depends("unitPrice") (1) ,当用户在用户界面中改变unitPrice 属性的值时, unitPriceInPesetas属性将重新计算,值将刷新显示给用户.
在计算属性中,你可以直接访问 JDBC 连接, 例如:
JDBC 代码确实丑陋笨拙,但有时确实有助于解决性能问题. DataSourceConnectionProvider 类允许你获取实体数据源的连接 (Invoice in this case).这个类只是为你提供方便, 你也可以使用JNDI或其他方式获取 JDBC连接.事实上,在计算属性中,你可以编写任何Java代码.
如果你使用基于属性的访问, 也就是注解类的getter或 setters, 你必须添加 @Transient 注解到对应的计算属性, 用这种方式:
缺省值计算器
使用@DefaultValueCalculator 你可以在属性上关联业务逻辑, 这种情况下属性是可读可写的.缺省值计算器用于计算初始值. 例如:当用户要创建一个新的Invoice (举例来说),他会发现year 字段已经有值了,他也可以进行修改. 生成值的而逻辑在 CurrentYearCalculator 类中:
通过设置属性值,可以定制计算器的行为:
在这种情况下, 计算缺省值时OpenXava首先 实例化StringCalculator然后将值 "GOOD" 注入 StringCalculator的string属性 , 最后调用 calculate() 方法来获取 relationWithSeller的缺省值. 你可以看出, 使用 @PropertyValue 注解,你可以创建可重用的计算器。
@PropertyValue 允许你从其他已显示的属性中注入值, 如是:
在这种情况下,执行计算器之前,OpenXava会将引用的drivingLicence的type属性的值注入CarrierRemarksCalculator的drivingLicenceType属性,As you see the from attribute supports qualified properties (reference.property).
@PropertyValue同样允许属性的值为空:
In this case OpenXava takes the value of the displayed property familyNumber and inject it in the property familyNumber of the calculator; that is @PropertyValue(name="familiyNumber") is equivalent to @PropertyValue(name="familiyNumber", from="familyNumber").
From a calculator you have direct access to JDBC connections, here is an example:
And the calculator class:
To use JDBC your calculator must implement IJDBCCalculator (1) and then it will receive an IConnectionProvider (2) that you can use within calculate().
OpenXava comes with a set of predefined calculators, you can find them in org.openxava.calculators.
创建时的默认值
You can indicate that the value will be calculated just before creating (inserting into database) an object for the first time.Usually for the key case you use the JPA standard. For example, if you want to use an identity (auto increment) column as key:
You can use other generation techniques, for example, a database sequence can be defined in this JPA standard way:
If you want to generate a unique identifier of type String and 32 characters, you can use a Hibernate extesion of JPA:
Look at section 9.1.9 of JPA 1.0 specification (part of JSR-220) for learning more about @GeneratedValues.
If you want to use your own logic for generating the value on create, or you want a generated new value for a non-key property then you cannot use the JPA @GeneratedValue, although it's easy to solve these cases using JPA. You only need to add this code to your class:
The JPA @PrePersist annotation does that this method will be executed before inserting the data the first time in database, in this method you can calculate the value for your key or non-key properties with your own logic.
属性校验器
A @PropertyValidator executes validation logic on the value assigned to the property just before storing. A property may have several validators:The technique to configure the validator (with @PropertyValue) is exactly the same than in calculators. With the attribute onlyOnCreate=”true” you can define that the validation will be executed only when the object is created, and not when it is modified.
The validator code is:
A validator has to implement IPropertyValidator (1), this obliges to the calculator to have a validate() method where the validation of property is executed. The arguments of validate() method are:
- Messages errors: A object of type Messages that represents a set of messages (like a smart collection) and where you can add the validation errors that you find.
- Object value: The value to validate.
- String objectName: Object name of the container of the property to validate. Useful to use in error messages.
- String propertyName: Name of the property to validate. Useful to use in error messages.
As you can see when you find a validation error you have to add it (with errors.add()) by sending a message identifier and the arguments. If you want to obtain a significant message you need to add to your i18n file the next entry:exclude_string={0} cannot contain {2} in {1}If the identifier sent is not found in the resource file, this identifier is shown as is; but the recommended way is always to use identifiers of resource files.The validation is successful if no messages are added and fails if messages are added. OpenXava collects all messages of all validators before saving and if there are messages, then it display them and does not save the object.
The package org.openxava.validators contains some common validators.
@PropertyValidator is defined as a Hibernate Validator contraint (new in v3.0.1).
默认校验器(new in v2.0.3)
You can define a default validator for properties depending of its type or stereotype. In order to do it you have to use the file xava/validators.xml of your project to define in it the default validators.For example, you can define in your xava/validators.xml the following:
In this case you are associating the validator PersonNameValidator to the stereotype PERSON_NAME. Now if you define a property as the next one:
This property will be validated using PersonNameValidator although the property itself does not define any validator. PersonNameValidator is applied to all properties with PERSON_NAME stereotype.
You can also assign a default validator to a type.
In validators.xml files you can also define the validators for determine if a required value is present (executed when you use @Required). Moreover you can assign names (alias) to validator classes.
You can learn more about validators examining OpenXava/xava/validators.xml and OpenXavaTest/xava/validators.xml.
Default validators do not apply when you use directly the JPA api for saving your entities.
参照(reference)
A reference allows access from an entity to another entity. A reference is translated to Java code as a property (with its getter and its setter) whose type is the referenced model Java type. For example a Customer can have a reference to his Seller, and that allows you to write code like this:to access to the name of the seller of that customer.
The syntax of reference is:
- @ManyToOne(optional=false) (JPA), @Required (OX) (optional, the JPA is the preferred one): Indicates if the reference is required. When saving OpenXava verifies if the required references are present, if not the saving is aborted and a list of validation errors is returned.
- @Id (JPA, optional): Indicates if the reference is part of the key. The combination of key properties and reference properties should map to a group of database columns with unique values, typically the primary key.
- @SearchKey (OX, optional): (New in v3.0.2) The search key references are used by the user as key for searching objects. They are editable in user interface of references allowing to the user type its value for searching. OpenXava uses the @Id members for searching by default, and if the id members are hidden then it uses the first property in the view. With @SearchKey you can choose explicitly references for searching.
- @DefaultValueCalculator (OX, one, optional): Implements the logic for calculating the initial value of the reference. This calculator must return the key value, that can be a simple value (only if the key of referenced object is simple) or key object (a special object that wraps the key).
- Reference declaration: A regular Java reference declaration with its getters and setters. The reference is marked with @ManyToOne (JPA) and the type must be another entity.
A little example of references:- A reference called seller to the entity of Seller entity.
- A reference called alternateSeller to the entity Seller. In this case we use fetch=FetchType.LAZY, in this way the data is readed from database on demand. This is the more efficient approach, but it's not the JPA default, therefore it's advisable to use always fetch=FetchType.LAZY when declaring the references.
If you assume that this is in an entity named Customer, you could write:参照中的默认值计算器
In a reference @DefaultValueCalculator works like in a property, only that it has to return the value of the reference key.For example, in the case of a reference with simple key, you can write:
The calculate() method is:
As you can see an integer is returned, that is, the default value for family is 2.
In the case of composed key:
And the calculator code:
Returns an object of type WarehouseKey.
将参照作为键
You can use references as key, or as part of the key. You have to declare the reference as @Id, and use an id class, as following:Also, you need to write your key class:
You need to write the key class although the key would be only a reference with only a join column.
It's better to use this feature only when you are working against legate databases, if you have control over the schema use an autogenerated id instead.
集合(Collections)
You can define a collection of references to entities. A collection is a Java property of type java.util.Collection.Here syntax for collection:
- @Size (HV, optional): Minimum (min) and/or maximum (max) number of expected elements. This is validated just before saving.
- @Condition (OX, optional): Restricts the elements that appear in the collection.
- @OrderBy (JPA, optional): The elements in collections will be in the indicated order.
- @XOrderBy (OX, optional): The @OrderBy of JPA does not allow to use qualified properties (properties of references). @XOrderBy does allow it.
- Collection declaration: A regular Java collection declaration with its getters and setters. The collection is marked with @OneToMany (JPA) or @ManyToMany (JPA) and the type must be another entity.
Let's have a look at some examples. First a simple one:If you have this within an Invoice, then you are defining a deliveries collection associated to that Invoice. The details to make the relationship are defined in the object/relational mapping.You use mappedBy="invoice" to indicate that the reference invoice of Delivery is used to mapping this collection.
Now you can write a code like this:
To do something with all deliveries associated to an invoice.
Let's look at another example a little more complex, but still in Invoice:
- Using REMOVE as cascade type produces that when the user removes an invoice its details are also removed.
- With @OrderBy you force that the details will be returned ordered by serviceType.
- The restriction @Size(min=1) requires at least one detail for the invoice to be valid.
You have full freedom to define how the collection data is obtained, with @Condition you can overwrite the default condition:If you have this collection within Carrier, you can obtain with this collection all carriers of the same warehouse but not himself, that is the list of his fellow workers. As you see you can use this in the condition in order to reference the value of a property of current object. @Condition only applied to the user interface generated by OpenXava, if you call directly to getFellowCarriers() it will be returns null.
If with this you have not enough, you can write the logic that returns the collection. The previous example can be written in the following way too:
As you see this is a conventional getter method. Obviously it must return a java.util.Collection whose elements are of type Carrier.
The references in collections are bidirectional, this means that if in a Seller you have a customers collection, then in Customer you must have a reference to Seller. But it's possible that in Customer you have more than one reference to Seller (for example, seller and alternateSeller) JPA does not know which to choose, becase of this you have the attribute mappedBy of @OneToMany. You can use it in this way:
To indicate that the reference seller and not alternateSeller will be used in this collection.
The @ManyToMany (JPA) anotation allows to define a collection with many-to-many multiplicity. As following:
In this case a customer have a collection of states, but a state can be present in several customers.
Methods
Methods are defined in an OpenXava entity (really a JPA entity) as in a regular Java class. For example:Methods are the sauce of the objects, without them the object only would be a silly wrapper of data. When possible it is better to put the business logic in methods (model layer) instead of in actions (controller layer).
Finders
A finder is a special static method that allows you to find an object or a collection of objects that follow some criteria.Some examples:
This methods can be used this way:
As you see, using finder methods creates a more readable code than using the verbose query API of JPA. But this is only a style recomendation, you can choose do not write finder methods and to use directly JPA queries.
Entity validator
An @EntityValidator allows to define a validation at model level. When you need to make a validation on several properties at a time, and that validation does not correspond logically with any of them, then you can use this type of validation.Its syntax is:
- value (required): Class that implements the validation logic. It has to be of type IValidator.
- onlyOnCreate (optional): If true the validator is executed only when creating a new object, not when an existing object is modified. The default value is false.
- properties (several @PropertyValue, optional): To set a value of the validator properties before executing it.
An example:And the validator code:
This validator must implement IValidator (1), this forces you to write a validate(Messages messages) (2). In this method you add the error message ids (3) (whose texts are in the i18n files). And if the validation process (that is the execution of all validators) produces some error, then OpenXava does not save the object and displays the errors to the user.
In this case you see how description and unitPrice properties are used to validate, for that reason the validation is at model level and not at individual property level, because the scope of validation is more than one property.
You can define more than one validator for entity using @EntityValidators, as following:
@EntityValidator is defined as a Hibernate Validator contraint (new in v3.0.1).
Remove validator
The @RemoveValidator is a level model validator too, but in this case it is executed just before removing an object, and it has the possibility to deny the deletion.Its syntax is:
- class (required): Class that implements the validation logic. Must implement IRemoveValidator.
- properties (several @PropertyValue, optional): To set the value of the validator properties before executing it.
An example can be:And the validator:
As you see this validator must implement IRemoveValidator (1) this forces you to write a setEntity() (2) method that receives the object to remove. If validation error is added to the Messages object sent to validate() (3) the validation fails. If after executing all validations there are validation errors, then OpenXava does not remove the object and displays a list of validation messages to the user.
In this case it verifies if there are deliveries that use this delivery type before deleting it.
As in the case of @EntityValidator you can use several @RemoveValidator for entity using @RemoveValidators annotation.
@RemoveValidator is executed when you remove entities from OpenXava (using MapFacade or standard OX actions), but not when you use directly JPA. If you want to create a restriction on remove which is recognized by JPA, just use @PreRemove JPA call method.
JPA callback methods
With @PrePersist you can plug in your own logic to execute just before creating the object as persistent object.As following:
In this case each time that a DeliveryType is created a suffix to description is added.
As you see, this is exactly the same as in other methods but is automatically executed just before creation.
With @PreUpdate you can plug in some logic to execute after the state of the object is changed and just before it is stored in the database, that is, just before executing UPDATE against database.
As following:
In this case whenever that a DeliveryType is modified a suffix is added to its description.
As you see, this is exactly the same as in other methods, but it is executed just before modifying.
You can use all the JPA callback annotations: @PrePersist, @PostPersist, @PreRemove, @PostRemove, @PreUpdate, @PostUpdate and @PostLoad.
Embeddable classes
As stated in JPA specification:"An entity may use other fine-grained classes to represent entity state. Instances of these classes, unlike
entity instances themselves, do not have persistent identity. Instead, they exist only as embedded objects
of the entity to which they belong. Such embedded objects belong strictly to their owning entity, and are
not sharable across persistent entities."
The embeddable class syntax is:
Embedded reference
This example is an embedded (annotated with @Embedded) Address that is referenced from the main entity.In the main entity you can write:
And you have to define the Address class as embeddable:
As you see an embeddable class can implement an interface (1) and contain references (2), among other things, but it cannot has persistent collections nor to use JPA callbacks methods.
This code can be used this way, for reading:
Or in this other way to set a new address:
In this case you have a simple reference (not collection), and the generated code is a simple JavaBean, whose life cycle is associated to its container object, that is, the Address is removed and created through the Customer. An Address never will have its own life and cannot be shared by other Customer.
Embedded collections
Embedded collections are not supported by JPA 1.0. But you can simulate them using collections to entities with cascade type REMOVE or ALL. OpenXava manages these collections in a special way, as they were embedded collections.Now an example of an embedded collection. In the main entity (for example Invoice) you can write:
Note that you use CascadeType.REMOVE, and InvoiceDetail is an entity, not an embeddable class:
As you see this is a complex entity, with calculators, validators, references and so on. Also you have to define a reference to the container class (invoice). In this case when an Invoice is removed all its details are removed too. Moreover there are differences at user interface level (you can learn more on view chapter).
继承
OpenXava 支持Java 和 JPA继承 .你可以按照例子定义一个@MappedSuperclass :
你还可以定义另一个@MappedSuperclass 继承了前一个类,例如:
现在你可以在你的实体类定义中使用Identifiable 或者 Nameable,如下所示:
现在,就是实体继承,一个实体继承了另一个实体:
You can create an OpenXava module for Human and Programmer (not for Identifiable or Nameble directly). In the Programmer module the user can only access to programmers, in the other hand using Human module the user can access to Human and Programmer objects. Moreover when the user tries to view the detail of a Programmer from the Human module the Programmer view will be show. True polymorphism.
About mapping, @AttributeOverrides is supported, but, at the moment, only a single table per class hierarchy mapping strategy works.
复合键
The preferred way for defining the key of an entity is a single autogenerated key (annotated with @Id and @GeneratedValue), but sometimes, for example when you go against legate database, you need to have an entity mapped to a table that uses several column as key. This case can be solved with JPA (therefore with OpenXava) in two ways, using @IdClass or using @EmbeddedIdId class
In this case you use @IdClass in your entity to indicate a key class, and you mark the key properties as @Id in your entity:You also need to declare your id class, a serializable regular class with all key properties from the entity:
Embedded id
In this case you have a reference to a @Embeddable object marked as @EmbeddedId:And you key is an embeddable class that holds the key properties:
Hibernate Validator (new in v3.0.1)
OpenXava has full support for Hibernate Validator. You can define your own constraints for your entities as explained in Hibernate Validator documention, and OpenXava will recognize them, showing the corresponding validation messages to the user.Moreover, the OpenXava annotations @Required, @PropertyValidator and @EntityValidator are defined as Hibernate Validator constraints, that means that when you save an entity using directly JPA these validations will apply.
In the other hand, @RemoveValidator, @PropertyValidator(onlyOnCreate=true), EntityValidator(onlyOnCreate=true) and the default validator feature of OpenXava are not recognized by Hibernate Validator or JPA, but only by OpenXava.